﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Net;
using Loong.AspNetCore.Models;
using Loong.AspNetCore.Mvc.Extensions;
using Loong.AspNetCore.Mvc.Results;
using Loong.Dependency;
using Loong.Domain.Entities;
using Loong.Extensions;
using Loong.Logging;
using Loong.Reflection;

namespace Loong.AspNetCore.Mvc.ExceptionHandling
{
    public class LoongExceptionFilter : IExceptionFilter, ITransientDependency
    {
        private readonly ILoggerFactory _loggerFactory;
        private readonly AspNetCoreOptions _options;
        private readonly IErrorInfoBuilder _errorInfoBuilder;

        public LoongExceptionFilter(
            ILoggerFactory loggerFactory,
            IOptions<AspNetCoreOptions> options,
            IErrorInfoBuilder errorInfoBuilder)
        {
            _loggerFactory = loggerFactory;
            _options = options.Value;
            _errorInfoBuilder = errorInfoBuilder;
        }

        public void OnException(ExceptionContext context)
        {
            if (!context.ActionDescriptor.IsControllerAction())
            {
                return;
            }

            var wrapResultAttribute =
                ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(
                    context.ActionDescriptor.GetMethodInfo(),
                    _options.DefaultWrapResultAttribute
                );

            if (wrapResultAttribute.LogError)
            {
                var declaringType = context.Exception.GetDeclaringType() ?? context.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo;
                ILogger logger = _loggerFactory.CreateLogger(declaringType);
                logger.LogException(context.Exception);
            }

            HandleAndWrapException(context, wrapResultAttribute);
        }

        protected virtual void HandleAndWrapException(ExceptionContext context, WrapResultAttribute wrapResultAttribute)
        {
            if (!ActionResultHelper.IsObjectResult(context.ActionDescriptor.GetMethodInfo().ReturnType))
            {
                return;
            }

            context.HttpContext.Response.StatusCode = GetStatusCode(context, wrapResultAttribute.WrapOnError);

            if (!wrapResultAttribute.WrapOnError)
            {
                return;
            }

            context.Result = new ObjectResult(new AjaxResponse(_errorInfoBuilder.BuildForException(context.Exception)));

            context.Exception = null; //Handled!
        }

        protected virtual int GetStatusCode(ExceptionContext context, bool wrapOnError)
        {
            if (context.Exception is EntityNotFoundException)
            {
                return (int)HttpStatusCode.NotFound;
            }

            if (wrapOnError)
            {
                return (int)HttpStatusCode.InternalServerError;
            }

            return context.HttpContext.Response.StatusCode;
        }
    }
}
